/**
 * 4阶矩阵 - L5
 * @author lonphy
 * @version 0.1
 */
(function(L5) {
	"use strict";
	class Matrix {
		constructor() {}
		/**
		 * 生成透视矩阵
		 * @param fov {Number} 垂直视角
		 * @param aspect {Number} 纵横比
		 * @param near {Number} 近截面
		 * @param far {Number} 远截面
		 * @returns {Float32Array}
		 */
		static perspective(fov, aspect, near, far) {
			var near2 = 2*near,
				t2b = near2 * Math.tan(fov * Math.PI / 360.0),
				r2l = t2b*aspect,
				f2n = far-near;
			return new Float32Array([
				near2/r2l,          0,                  0,                      0,
				0,                  near2/t2b,          0,                      0,
				0,                  0,                  -(far+near)/f2n,       -1,
				0,                  0,                  -far*near2/f2n,         0
			]);
		}
		/**
		 * 生成透视矩阵
		 * @param left {Number} 左平面
		 * @param right {Number} 右平面
		 * @param bottom {Number} 底平面
		 * @param top {Number} 上平面
		 * @param near {Number} 近平面
		 * @param far {Number} 远平面
		 * @returns {Float32Array}
		 */
		static frustum(left, right, bottom, top, near, far) {
			let near2 = 2*near, r2l = right - left, t2b = top-bottom, f2n = far-near;
			return new Float32Array([
				near2/r2l,          0,                  0,                      0,
				0,                  near2/t2b,          0,                      0,
				(right+left)/r2l,   (top+bottom)/t2b,   -(far+near)/f2n,       -1,
				0,                  0,                  -far*near2/f2n,         0
			]);
		}
		/**
		 * 生成正交投影矩阵
		 * @param left {Number} 左平面
		 * @param right {Number} 右平面
		 * @param bottom {Number} 底平面
		 * @param top {Number} 上平面
		 * @param near {Number} 近平面
		 * @param far {Number} 远平面
		 * @returns {Float32Array}
		 */
		static ortho(left, right, bottom, top, near, far) {
			let f2n = far-near;
			let t2b = top-bottom;
			let r2l = right-left;

			return new Float32Array([
				2 /r2l,              0,                  0,                    0,
				0,                   2 /t2b,             0,                    0,
				0,                   0,                  -2/f2n,               0,
				-(right+left)/r2l,  -(top+bottom)/t2b,   -(far+near)/f2n,      1
			]);
		}
		/**
		 * 生成投影矩阵
		 * @param eye {L5.Vec3} 摄像机位置
		 * @param at {L5.Vec3} 观察的目标位置点
		 * @param up {L5.Vec3} 摄影坐标系Y轴上方向
		 * @returns {Float32Array}
		 */
		static lookAt(eye, at, up) {
			let zs = eye.clone().direction(at);
			let xs = up.cross(zs).normalize();
			let ys = zs.clone().cross(xs).normalize();

			return Matrix.multiply(
				[
					xs.x,    ys.x,     zs.x,    0,
					xs.y,    ys.y,     zs.y,    0,
					xs.z,    ys.z,     zs.z,    0,
					0,       0,        0,       1
				], [
					1,        0,       0,      0,
					0,        1,       0,      0,
					0,        0,       1,      0,
					-eye.x,   -eye.y,  -eye.z, 1
				]
			);
		}
		/**
		 * 返回单位矩阵
		 * @returns {Float32Array}
		 */
		static identity() {
			return new Float32Array([
				1,  0,  0,  0,
				0,  1,  0,  0,
				0,  0,  1,  0,
				0,  0,  0,  1
			]);
		}
		/**
		 * 矩阵乘法
		 * @param m1 {Float32Array}
		 * @param m2 {Float32Array}
		 * @returns {Float32Array}
		 */
		static multiply(m1, m2) {
			let a11=m1[0 ], a21=m1[1 ], a31=m1[2 ], a41=m1[3 ],
				a12=m1[4 ], a22=m1[5 ], a32=m1[6 ], a42=m1[7 ],
				a13=m1[8 ], a23=m1[9 ], a33=m1[10], a43=m1[11],
				a14=m1[12], a24=m1[13], a34=m1[14], a44=m1[15],

				b11=m2[0 ], b21=m2[1 ], b31=m2[2 ], b41=m2[3 ],
				b12=m2[4 ], b22=m2[5 ], b32=m2[6 ], b42=m2[7 ],
				b13=m2[8 ], b23=m2[9 ], b33=m2[10], b43=m2[11],
				b14=m2[12], b24=m2[13], b34=m2[14], b44=m2[15],
				ret = new Float32Array(16);

			ret[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
			ret[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
			ret[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
			ret[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
			ret[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
			ret[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
			ret[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
			ret[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
			ret[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
			ret[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
			ret[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
			ret[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
			ret[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;
			ret[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;
			ret[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;
			ret[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;
			return ret;
		}
		/**
		 * 绕Y轴旋转指定角度
		 * @param theta {Number} 旋转角度， 弧度制
		 * @returns {Float32Array}
		 */
		static rotateY(theta) {
			let s = Math.sin(theta), c = Math.cos(theta);
			return new Float32Array([
				c,    0,   -s,    0,
				0,    1,    0,    0,
				s,    0,    c,    0,
				0,    0,    0,    1
			]);
		}
		/**
		 * 绕X轴旋转指定角度
		 * @param theta {Number} 旋转角度， 弧度制
		 * @returns {Float32Array}
		 */
		static rotateX(theta) {
			let s = Math.sin(theta), c = Math.cos(theta);
			return new Float32Array([
				1,    0,    0,    0,
				0,    c,    s,    0,
				0,   -s,    c,    0,
				0,    0,    0,    1
			]);
		}
		/**
		 * 绕Z轴旋转指定角度
		 * @param theta {Number} 旋转角度， 弧度制
		 * @returns {Float32Array}
		 */
		static rotateZ(theta) {
			let s = Math.sin(theta), c = Math.cos(theta);
			return new Float32Array([
				c,    s,    0,    0,
			   -s,    c,    0,    0,
				0,    0,    1,    0,
				0,    0,    0,    1
			]);
		}
		/**
		 * 绕axis轴
		 * @param axis {L5.Vec3} 旋转轴
		 * @param theta {Number} 旋转角度， 弧度制
		 * @returns {Float32Array}
		 */
		static rotate(axis, theta) {
			axis = axis.clone().normalize();
			let x = axis.x, y = axis.y, z = axis.z,
				c = Math.cos(theta),
				c1 = 1-c,
				s = Math.sin(theta),
				xc1=x*c1, yc1=y*c1, zc1=z*c1,
				xs=x*s, ys=y*s, zs=z*s;
			return new Float32Array([
				x*xc1+c,   x*yc1+zs,     x*zc1-ys,    0,
				y*xc1-zs,  y*yc1+c,      y*zc1+xs,    0,
				z*xc1+ys,  z*yc1-xs,     z*zc1+c,     0,
				0,           0,              0,       1
			]);
		}
		/**
		 * 平移
		 * @param x {Number} x轴移动距离
		 * @param y {Number} y轴移动距离
		 * @param z {Number} z轴移动距离
		 * @returns {Float32Array}
		 */
		static translate(x,y,z) {
			return new Float32Array([
				1,    0,    0,    0,
				0,    1,    0,    0,
				0,    0,    1,    0,
				x,    y,    z,    1
			]);
		}
		/**
		 * 缩放
		 * @param sx {Number} x轴缩放系数
		 * @param sy {Number} y轴缩放系数
		 * @param sz {Number} z轴缩放系数
		 * @returns {Float32Array}
		 */
		static scale(sx, sy, sz) {
			sx = sx || 1;
			sz = sz || 1;
			return new Float32Array([
			   sx,    0,    0,    0,
				0,   sy,    0,    0,
				0,    0,   sz,    0,
				0,    0,    0,    1
			]);
		}
		/**
		 * 转置
		 * @param input {Float32Array} 输入矩阵
		 * @returns {Float32Array} 输出矩阵
		 */
		static transpose(input) {
			return new Float32Array([
				input[0],  input[4],  input[8],  input[12],
				input[1],  input[5],  input[9],  input[13],
				input[2],  input[6],  input[10], input[14],
				input[3],  input[7],  input[11], input[15]
			]);
		}
		/**
		 * 求矩阵的逆
		 * @param input {Float32Array} 输入矩阵
		 * @returns {Float32Array} 输出矩阵
		 */
		static inverse(input) {
			let h=input[0 ], e=input[1 ], c=input[2 ], a=input[3 ],
				s=input[4 ], q=input[5 ], p=input[6 ], o=input[7 ],
				x=input[8 ], w=input[9 ], v=input[10], u=input[11],
				l=input[12], i=input[13], f=input[14], d=input[15];
			let ret = new Float32Array(16);
			ret[0 ] = q*v*d + p*u*i + o*w*f - q*u*f - p*w*d - o*v*i;
			ret[1 ] = e*u*f + c*w*d + a*v*i - e*v*d - c*u*i - a*w*f;
			ret[2 ] = e*p*d + c*o*i + a*q*f - e*o*f - c*q*d - a*p*i;
			ret[3 ] = e*o*v + c*q*u + a*p*w - e*p*u - c*o*w - a*q*v;
			ret[4 ] = s*u*f + p*x*d + o*v*l - s*v*d - p*u*l - o*x*f;
			ret[5 ] = h*v*d + c*u*l + a*x*f - h*u*f - c*x*d - a*v*l;
			ret[6 ] = h*o*f + c*s*d + a*p*l - h*p*d - c*o*l - a*s*f;
			ret[7 ] = h*p*u + c*o*x + a*s*v - h*o*v - c*s*u - a*p*x;
			ret[8 ] = s*w*d + q*u*l + o*x*i - s*u*i - q*x*d - o*w*l;
			ret[9 ] = h*u*i + e*x*d + a*w*l - h*w*d - e*u*l - a*x*i;
			ret[10] = h*q*d + e*o*l + a*s*i - h*o*i - e*s*d - a*q*l;
			ret[11] = h*o*w + e*s*u + a*q*x - h*q*u - e*o*x - a*s*w;
			ret[12] = s*v*i + q*x*f + p*w*l - s*w*f - q*v*l - p*x*i;
			ret[13] = h*w*f + e*v*l + c*x*i - h*v*i - e*x*f - c*w*l;
			ret[14] = h*p*i + e*s*f + c*q*l - h*q*f - e*p*l - c*s*i;
			ret[15] = h*q*v + e*p*x + c*s*w - h*p*w - e*s*v - c*q*x;

			let t = h*q*v*d + h*p*u*i + h*o*w*f + e*s*u*f + e*p*x*d +
					e*o*v*l + c*s*w*d + c*q*u*l + c*o*x*i + a*s*v*i +
					a*q*x*f + a*p*w*l - h*q*u*f - h*p*w*d - h*o*v*i -
					e*s*v*d - e*p*u*l - e*o*x*f - c*s*u*i - c*q*x*d -
					c*o*w*l - a*s*w*f - a*q*v*l - a*p*x*i;
			var m = 1 / t;
			ret[0 ]*=m;    ret[1 ]*=m;    ret[2 ]*=m;    ret[3 ]*=m;
			ret[4 ]*=m;    ret[5 ]*=m;    ret[6 ]*=m;    ret[7 ]*=m;
			ret[8 ]*=m;    ret[9 ]*=m;    ret[10]*=m;    ret[11]*=m;
			ret[12]*=m;    ret[13]*=m;    ret[14]*=m;    ret[15]*=m;
			return ret;
		}
		static print(input) {
			let out = 'Matrix:\n';
			out += '|\t'+ input[0 ] +'\t'+ input[1 ] +'\t'+ input[2 ] +'\t'+ input[3 ]  + '\t|\n';
			out += '|\t'+ input[4 ] +'\t'+ input[5 ] +'\t'+ input[6 ] +'\t'+ input[7 ]  + '\t|\n';
			out += '|\t'+ input[8 ] +'\t'+ input[9 ] +'\t'+ input[10] +'\t'+ input[11]  + '\t|\n';
			out += '|\t'+ input[12] +'\t'+ input[13] +'\t'+ input[14] +'\t'+ input[15]  + '\t|';
			console.log(out);
		}
	}
	L5.Matrix = Matrix;
})(window.L5);
